﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Xml.Linq;
using System.Xml;
using System.Xml.XPath;

namespace Sayeet
{
    public class AppData
    {
        public RosterWindow mainWindow;
        public ChatWindow chatWindow;
        public DebugWindow debugWindow;
        //public string myJid = "me@jabber.org";
        public JID myJid = new JID(Sayeet.Properties.Settings.Default.Login, Sayeet.Properties.Settings.Default.Server);

        public XMPPConnection connection = null;
        private bool connectionStarted = false;
        public bool Connected = false;


        public List<RosterItem> contacts = new List<RosterItem>();
        public List<RosterItem> unknownContacts = new List<RosterItem>();

        public void startConnection()
        {
            if (!connectionStarted)
            {
                if (connection == null)
                {
                    connection = new XMPPConnection(this);
                    connection.StateChanged += new StateChangedEventHandler(connection_StateChanged);
                    //connection.RcvQueueUpdated += new RcvQueueUpdatedEventHandler(connection_RcvQueueUpdated);
                    mainWindow.AddRcvQueueHandler();
                }
                connection.Address = Sayeet.Properties.Settings.Default.Server;
                connection.Port = Sayeet.Properties.Settings.Default.Port;
                connection.Connect();
                connectionStarted = true;
            }
        }

        private void connection_RcvQueueUpdated(XMPPConnection conn)
        {
            XMPPMessage m = null;
            while ((m = conn.GetMessage()) != null)
                ProcessMessage(m, conn);
        }

        public void ProcessMessage(XMPPMessage m, XMPPConnection conn)
        {
            XNamespace jc = "jabber:client";
            if (m != null)
            {
                switch (m.stanza.Name.LocalName)
                {
                    case "features":
                        debugWindow.addText("features read", "+++ ");

                        IEnumerable<XElement> mechanisms = m.stanza.XPathSelectElements(".//*");
                        foreach (XElement mechanism in mechanisms)
                        {
                            if (mechanism.Value == "DIGEST-MD5")
                                connection.AcceptsDigest = true;
                            if (mechanism.Value == "PLAIN")
                                connection.AcceptsPlaintext = true;
                        }

                        conn.LogIn();
                        break;
                    case "iq":
                        switch (m.stanza.Attribute("type").Value)
                        {
                            case "result":
                                if (m.stanza.HasElements)
                                {
                                    XElement query = (XElement)m.stanza.FirstNode;
                                    switch (query.Attribute("xmlns").Value)
                                    {
                                        case "jabber:iq:auth":
                                            {
                                                debugWindow.addText("sending auth response");
                                                //	Send:<iq type='set' id='mx_login'><query xmlns='jabber:iq:auth'>
                                                //    <username>gnauck</username><digest>27c05d464e3f908db3b2ca1729674bfddb28daf2</digest>
                                                //    <resource>Office</resource></query></iq>
                                                XNamespace auth = "jabber:iq:auth";
                                                XElement response = new XElement("iq",
                                                    new XAttribute("type", "set"),
                                                    new XAttribute("id", "login1"),
                                                    new XElement(auth + "query",
                                                        new XElement(auth + "username", Sayeet.Properties.Settings.Default.Login),
                                                        new XElement(auth + "password", Sayeet.Properties.Settings.Default.Password),
                                                        new XElement(auth + "resource", "Sayeet")
                                                        )
                                                    );
                                                conn.Send(new XMPPMessage(response));
                                            }
                                            break;
                                        case "jabber:iq:roster":
                                            {
                                                debugWindow.addText("reading roster");
                                                UpdateRoster(query);
                                            }
                                            break;
                                        default:
                                            debugWindow.addText("query of unhandled type", "--- ");
                                            break;
                                    }
                                }
                                else
                                {
                                    string id = m.stanza.Attribute("id").Value;
                                    if (id == conn.LoginID)
                                    {
                                        // Logged in!
                                        debugWindow.addText("login successful", ">>> ");
                                        // Set presence:
                                        GetRoster();
                                        SetPresence(XMPPConnection.Status.online);
                                    }
                                    else
                                    {
                                        debugWindow.addText("id: " + m.stanza.Attribute("id").Value + " successful", ">>> ");
                                    }
                                }
                                break;
                            case "set":
                                if (m.stanza.HasElements)
                                {
                                    XElement query = (XElement)m.stanza.FirstNode;
                                    switch (query.Attribute("xmlns").Value)
                                    {
                                        case "jabber:iq:roster":
                                            debugWindow.addText("iq:set", "+++ ");
                                            XNamespace ns = "jabber:iq:roster";
                                            JID j = new JID(query.Element(ns + "item").Attribute("jid").Value);
                                            RosterItem ri = GetRosterItemByJID(j);
                                            bool update = true;
                                            if (ri == null)
                                            {
                                                ri = new RosterItem(this);
                                            }
                                            ri.Jid = j;
                                            switch (query.Element(ns + "item").Attribute("subscription").Value)
                                            {
                                                case "from":
                                                    ri.Subscription = RosterItem.enSubscription.from;
                                                    break;
                                                case "to":
                                                    ri.Subscription = RosterItem.enSubscription.to;
                                                    break;
                                                case "both":
                                                    ri.Subscription = RosterItem.enSubscription.both;
                                                    break;
                                                case "remove":
                                                    update = false;
                                                    removeContact(ri);
                                                    break;
                                                default:
                                                    ri.Subscription = RosterItem.enSubscription.none;
                                                    break;
                                            }
                                            ri.Name = ri.Jid.ToStringWithoutResource();
                                            if (query.Element(ns + "group") != null)
                                                ri.Group = query.Element(ns + "group").Value;
                                            else
                                                ri.Group = "";
                                            if (update)
                                                updateContact(ri);

                                            break;
                                        default:
                                            break;
                                    }
                                }
                                break;
                            default:
                                debugWindow.addText("iq of unhandled type", "--- ");
                                break;
                        }
                        break;
                    case "message":
                        switch (m.stanza.Attribute("type").Value)
                        {
                            case "chat":
                                {
                                    ProcessChatMessage(m.stanza);
                                }
                                break;
                            default:
                                debugWindow.addText("message of unhandled type", "--- ");
                                break;
                        }
                        break;
                    case "presence":
                        if (m.stanza.Attribute("type") != null)
                        {
                            switch (m.stanza.Attribute("type").Value)
                            {
                                case "subscribe":
                                    JID j = new JID(m.stanza.Attribute("from").Value);
                                    bool response = askSubscription(j);
                                    connection.Send(XMPPMessage.CreateSubscriptionResponse(j.ToStringWithoutResource(), response));
                                    if (response)
                                    {
                                        connection.Send(XMPPMessage.CreateSubscriptionRequest(j.ToStringWithoutResource()));
                                    }
                                    break;

                                case "subscribed":
                                    // TODO: someone accepted subscription!
                                    break;

                                case "unsubscribed":
                                    // TODO: someone rejected subscription!
                                    break;

                                case "unsubscribe":
                                    // TODO: someone cancelled subscription!
                                    break;

                                default:
                                    debugWindow.addText("unhandled presence: " + m.stanza.ToString(), "--- ");
                                    break;
                            }
                        }
                        else
                        {
                            JID from = new JID(m.stanza.Attribute("from").Value);
                            XMPPConnection.Status status;

                            if (m.stanza.Element(jc + "show") != null)
                            {
                                string show = m.stanza.Element(jc + "show").Value;
                                switch (show)
                                {
                                    case "away":
                                        status = XMPPConnection.Status.away;
                                        break;
                                    default:
                                        status = XMPPConnection.Status.offline;
                                        break;
                                }
                            }
                            else
                            {
                                status = XMPPConnection.Status.online;
                            }

                            RosterItem i = GetRosterItemByJID(from);
                            if (i != null)
                                i.SetStatus(status);
                            debugWindow.addText(from.ToString() + " has changed status to " + status.ToString(), "'''");
                        }
                        break;
                    default:
                        debugWindow.addText("unhandled message: " + m.stanza.Name.LocalName, "--- ");
                        break;
                }
            }
        }

        private void updateContact(RosterItem contact)
        {
            RosterItem existingContact = GetRosterItemByJID(contact.Jid);
            if (existingContact == null)
                addContact(contact);
            else
            {
                existingContact = contact;
                mainWindow.updateList();
            }
        }

        private RosterItem GetRosterItemByJID(JID j)
        {
            foreach (RosterItem i in contacts)
            {
                if (i.Jid.ToStringWithoutResource() == j.ToStringWithoutResource())
                    return i;
            }

            return null;
        }

        private void ProcessChatMessage(XElement message)
        {
            XNamespace jc = "jabber:client";
            XElement body;
            string fromJid = message.Attribute("from").Value;
            JID j = new JID(fromJid);

            if ((body = message.Element(jc + "body")) != null)
            {
                debugWindow.addText(body.Value, "~~~ " + j.ToString() + ": ");
                ReceiveMessage(j, body.Value);
            }
        }

        public void SendChatMessage(JID to, string body)
        {
            connection.Send(XMPPMessage.CreateChatMessage(to.ToString(), myJid.ToString(), body));
        }

        private void UpdateRoster(XElement query)
        {
            clearRoster();
            try
            {
                IEnumerable<XElement> nodes = query.Elements();
                foreach (XElement node in nodes)
                {
                    XNamespace ns = "jabber:iq:roster";
                    RosterItem contact = new RosterItem(this);
                    contact.Jid = new JID((string)node.Attribute("jid"));
                    contact.Name = (string)node.Attribute("name");
                    switch ((string)node.Attribute("subscription"))
                    {
                        case "both":
                            contact.Subscription = RosterItem.enSubscription.both;
                            break;
                        case "from":
                            contact.Subscription = RosterItem.enSubscription.from;
                            break;
                        case "to":
                            contact.Subscription = RosterItem.enSubscription.to;
                            break;
                        default:
                            contact.Subscription = RosterItem.enSubscription.none;
                            break;
                    }
                    if (node.HasElements)
                    {
                        contact.Group = node.Element(ns+"group").Value;
                    }
                    else
                        contact.Group = "Ungrouped";

                    addContact(contact);

                    //IEnumerable<XNode> items = query.Nodes
                    //if (node.Parent == query)
                    //debugWindow.addText("node read: " + node.ToString());


                }
            }
            catch (Exception)
            {
                debugWindow.addText("Roster Add Exception");
            }
        }

        private void clearRoster()
        {
            contacts.Clear();
        }

        public void GetRoster()
        {
            //<iq from='juliet@example.com/balcony' type='get' id='roster_1'>
            //    <query xmlns='jabber:iq:roster'/>
            //</iq>
            XNamespace ns = "jabber:iq:roster";

            XElement request = new XElement("iq",
                new XAttribute("from", myJid.ToString()),
                new XAttribute("type", "get"),
                new XAttribute("id", "initialroster"),
                new XElement(ns + "query")
                );

            connection.Send(new XMPPMessage(request));
        }

        public void SetPresence(XMPPConnection.Status status)
        {
            if (Connected == true && status != connection.status)
            {
                XNamespace ns = "jabber:client";
                XElement presence;
                switch (status)
                {
                    default:
                    case XMPPConnection.Status.online:
                        presence = new XElement(ns + "presence"//,
                            //new XAttribute("to", connection.Address)
                            //new XElement("show", p)
                            );
                        break;
                    case XMPPConnection.Status.away:
                        presence = new XElement(ns + "presence",
                            //new XAttribute("to", connection.Address)
                            new XElement("show", "away")
                            );
                        break;
                }
                connection.Send(new XMPPMessage(presence));
                connection.status = status;
                mainWindow.setStatus(status.ToString());
            }
        }

        internal void stopConnection()
        {
            if (connectionStarted)
            {
                connection.Disconnect();
                connectionStarted = false;
            }
            contacts.Clear();
            mainWindow.updateList();
        }

        private void connection_StateChanged(XMPPConnection.ThreadState ts)
        {
            mainWindow.setStatus(ts.ToString());
            debugWindow.addText(ts.ToString());
        }

        public void newContact(RosterItem contact)
        {
            //TODO: XMPP New COntact
            connection.Send(XMPPMessage.CreateRosterSet(contact.Jid.ToStringWithoutResource(), contact.Name, contact.Group));
            connection.Send(XMPPMessage.CreateSubscriptionRequest(contact.Jid.ToStringWithoutResource()));
            //contacts.Add(contact);
            //mainWindow.updateList();
        }

        public void addContact(RosterItem contact)
        {
            debugWindow.addText("add Contact");
            contacts.Add(contact);
            mainWindow.updateList();
        }

        public void removeContact(RosterItem contact)
        {
            connection.Send(XMPPMessage.CreateRosterDelete(myJid.ToString(), contact.Jid.ToStringWithoutResource()));
            contacts.Remove(contact);
            mainWindow.updateList();
        }

        public void ReceiveMessage(JID jid, string message)
        {
            RosterItem targetContact = null;
            foreach (RosterItem contact in contacts)
            {
                if (contact.Jid.ToStringWithoutResource() == jid.ToStringWithoutResource())
                    targetContact = contact;
            }
            if (targetContact == null)
            {
                foreach (RosterItem contact in unknownContacts)
                {
                    if (contact.Jid.ToStringWithoutResource() == jid.ToStringWithoutResource())
                        targetContact = contact;
                }
            }
            if (targetContact == null)
            {
                targetContact = new RosterItem(jid, "", "", this);
                unknownContacts.Add(targetContact);
            }

            targetContact.receiveMessage(message);

        }

        public bool askSubscription(JID jid)
        {
            SubscriptionPopUp popUp = new SubscriptionPopUp();
            popUp.setJid(jid);
            System.Windows.Forms.DialogResult result = popUp.ShowDialog();

            if (result == System.Windows.Forms.DialogResult.Yes)
            {
                return true;
            }
            else
            {
                return false;
            }
        }



    }


}
